package lvm

import (
	"errors"
	"log"
	"os"
	"os/exec"
	"slices"
)

func FindCurrentLVM() ([]*VgItem, error) {
	VG := CommandVGS()
	result, errmsg, err := VG.Run()
	if err != nil {
		log.Printf("failed to run vgs command: %s \n%s\n", err, errmsg)
		return nil, err
	}

	if len(result.Report) == 0 || len(result.Report[0].Vg) == 0 {
		err = errors.New("no volume groups found")
		return nil, err
	}

	var vgs []*VgItem
	for _, vg := range result.Report[0].Vg {
		if vg.PvCount == "0" || vg.LvCount == "0" {
			continue
		}
		vgs = append(vgs, &vg)
	}

	if len(vgs) == 0 {
		err = errors.New("no valid volume groups found")
		return nil, err
	}

	return vgs, nil
}

func FindUnmountedDevices() (map[string]*BlkPart, error) {
	lblkCmd := CommandLBLK()
	result, errmsg, err := lblkCmd.Run()
	if err != nil {
		log.Printf("failed to run lsblk command: %s \n%s\n", err, errmsg)
		return nil, err
	}

	var unmountedDevices map[string]*BlkPart = make(map[string]*BlkPart)
	var unmountedPart func(part BlkPart) bool
	unmountedPart = func(part BlkPart) bool {
		if len(part.Mountpoints) > 0 {
			return false
		}

		if len(part.Mountpoints) == 0 && len(part.Children) == 0 {
			return true
		}

		for _, child := range part.Children {
			if !unmountedPart(child) {
				return false
			}
		}
		return true
	}

	for _, dev := range result.Blockdevices {
		if dev.Type != "disk" {
			continue
		}

		if unmountedPart(dev) {
			unmountedDevices["/dev/"+dev.Name] = &dev
		}
	}

	return unmountedDevices, nil
}

func FindLvByVgName(vgName string) ([]*LvItem, error) {
	LV := CommandLVS()
	result, errmsg, err := LV.Run("-o", "+lv_dm_path,lv_path")
	if err != nil {
		log.Printf("failed to run lvs command: %s \n%s\n", err, errmsg)
		return nil, err
	}

	if len(result.Report) == 0 || len(result.Report[0].Lv) == 0 {
		return nil, nil
	}

	var lvs []*LvItem
	for _, lv := range result.Report[0].Lv {
		if lv.VgName == vgName {
			mountpoints, err := FindMountpointsByLvDmPath(lv.LvDmPath)
			if err == nil {
				lv.Mountpoints = mountpoints
			}
			lvs = append(lvs, &lv)
		}
	}

	return lvs, nil
}

func FindMountpointsByLvDmPath(lvDmPath string) ([]string, error) {
	FINDMNT := CommandFindMnt()
	result, errmsg, err := FINDMNT.Run(lvDmPath)
	if err != nil && errmsg != "" {
		log.Printf("failed to run findmnt command: %s \n%s\n", err, errmsg)
		return nil, err
	}

	if result == nil || len(result.Filesystems) == 0 {
		return nil, nil
	}

	var mountpoints []string
	for _, fs := range result.Filesystems {
		mountpoints = append(mountpoints, fs.Target)
	}

	return mountpoints, nil
}

/*
wipefs -a /dev/nvme0n1
sgdisk --zap-all /dev/nvme0n1
*/
func DeleteDevicePartitions(devicePath string) error {
	c, err := exec.Command("wipefs", "-a", devicePath).CombinedOutput()
	if err != nil {
		log.Printf("failed to wipe device %s: %s\n", devicePath, c)
		return err
	}

	// c, err = exec.Command("sgdisk", "--zap-all", devicePath).CombinedOutput()
	// if err != nil {
	// 	log.Printf("failed to zap device %s: %s\n", devicePath, c)
	// 	return err
	// }

	return nil
}

/*
sudo parted /dev/sdX mklabel gpt
sudo parted -a optimal /dev/sdX mkpart primary 1MiB 100%
*/
func MakePartOnDevice(devicePath string) error {
	c, err := exec.Command("parted", "-s", devicePath, "mklabel", "gpt").CombinedOutput()
	if err != nil {
		log.Printf("failed to make partition table on device %s: %s\n", devicePath, c)
		return err
	}

	c, err = exec.Command("parted", "-a", "optimal", devicePath, "mkpart", "primary", "1MiB", "100%").CombinedOutput()
	if err != nil {
		log.Printf("failed to make partition on device %s: %s\n", devicePath, c)
		return err
	}

	return nil
}

/*
sudo pvcreate /dev/sdX1
sudo vgextend target_vg /dev/sdX1
*/
func AddNewPV(devicePath string, vg string) error {
	partition := devicePath + "p1"
	c, err := exec.Command("pvcreate", "-f", partition).CombinedOutput()
	if err != nil {
		log.Printf("failed to create physical volume on device %s: %s\n", partition, c)
		return err
	}

	c, err = exec.Command("vgextend", vg, partition).CombinedOutput()
	if err != nil {
		log.Printf("failed to extend volume group %s with device %s: %s\n", vg, partition, c)
		return err
	}

	return nil
}

/*
lvextend -l +100%FREE "/dev/$VG_NAME/$LV_ROOT_NAME"
resize2fs "/dev/$VG_NAME/$LV_ROOT_NAME"
*/
func ExtendLv(vg, lv string) error {
	c, err := exec.Command("lvextend", "-l", "+100%FREE", "/dev/"+vg+"/"+lv).CombinedOutput()
	if err != nil {
		log.Printf("failed to extend logical volume %s in volume group %s: %s\n", lv, vg, c)
		return err
	}

	c, err = exec.Command("resize2fs", "/dev/"+vg+"/"+lv).CombinedOutput()
	if err != nil {
		log.Printf("failed to resize filesystem on logical volume %s in volume group %s: %s\n", lv, vg, c)
		return err
	}

	return nil
}

func DeactivateLv(vg string) error {
	c, err := exec.Command("lvchange", "-an", vg).CombinedOutput()
	if err != nil {
		log.Printf("failed to deactivate logical volume in volume group %s: %s\n", vg, c)
		return err
	}

	return nil
}

func RemoveLv(lvpath string) error {
	_, err := os.Stat(lvpath)
	if err != nil {
		if os.IsNotExist(err) {
			return nil
		}
		log.Printf("failed to stat logical volume %s: %s\n", lvpath, err)
		return err
	}

	c, err := exec.Command("lvremove", "-f", lvpath).CombinedOutput()
	if err != nil {
		log.Printf("failed to remove logical volume %s: %s\n", lvpath, c)
		return err
	}

	return nil
}

func RemoveVg(vg string) error {
	c, err := exec.Command("vgremove", "-f", vg).CombinedOutput()
	if err != nil {
		log.Printf("failed to remove volume group %s: %s\n", vg, c)
		return err
	}

	return nil
}

func RemovePv(pv string) error {
	c, err := exec.Command("pvremove", "-f", pv).CombinedOutput()
	if err != nil {
		log.Printf("failed to remove physical volume %s: %s\n", pv, c)
		return err
	}

	return nil
}

func FindVgsOnDevice(devicePaths []string) ([]*VgItem, error) {
	VG := CommandVGS()
	result, errmsg, err := VG.Run("-o", "+pv_name")
	if err != nil {
		log.Printf("failed to run vgs command: %s \n%s\n", err, errmsg)
		return nil, err
	}

	var vgs []*VgItem
	for _, vg := range result.Report[0].Vg {
		if slices.Contains(devicePaths, vg.PvName) {
			vgs = append(vgs, &vg)
		}
	}

	return vgs, nil
}
